// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef CORE_INTERNAL_MEDIUMS_BLE_H_
#define CORE_INTERNAL_MEDIUMS_BLE_H_

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "absl/base/thread_annotations.h"
#include "absl/container/btree_map.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/functional/any_invocable.h"
#include "absl/strings/string_view.h"
#include "connections/implementation/mediums/ble/advertisement_read_result.h"
#include "connections/implementation/mediums/ble/ble_advertisement.h"
#include "connections/implementation/mediums/ble/ble_socket.h"
#include "connections/implementation/mediums/ble/discovered_peripheral_callback.h"
#include "connections/implementation/mediums/ble/discovered_peripheral_tracker.h"
#include "connections/implementation/mediums/ble/instant_on_lost_manager.h"
#include "connections/implementation/mediums/bluetooth_radio.h"
#include "connections/implementation/pcp.h"
#include "connections/power_level.h"
#include "internal/platform/ble.h"
#include "internal/platform/bluetooth_adapter.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancelable_alarm.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/expected.h"
#include "internal/platform/implementation/ble.h"
#include "internal/platform/multi_thread_executor.h"
#include "internal/platform/mutex.h"
#include "internal/platform/mutex_lock.h"
#include "internal/platform/runnable.h"
#include "internal/platform/scheduled_executor.h"
#include "internal/platform/single_thread_executor.h"

namespace nearby {
namespace connections {

// Provides the operations that can be performed on the Bluetooth Low Energy
// (BLE) medium.
class Ble final {
 public:
  using DiscoveredPeripheralCallback = mediums::DiscoveredPeripheralCallback;

  // Callback that is invoked when a new connection is accepted.
  using AcceptedConnectionCallback =
      absl::AnyInvocable<void(BleSocket socket, const std::string& service_id)>;

  // Callback that is invoked when a new l2cap connection is accepted.
  using AcceptedL2capConnectionCallback = absl::AnyInvocable<void(
      BleL2capSocket socket, const std::string& service_id)>;

  // The refactor version of AcceptedConnectionCallback and
  // AcceptedL2capConnectionCallback above.
  using AcceptedConnectionCallback2 =
      absl::AnyInvocable<void(std::unique_ptr<mediums::BleSocket> socket,
                              const std::string& service_id)>;

  // The type of the BLE advertising. In current implementation, we don't
  // support multiple advertising types on a Medium instance.
  enum class AdvertisingType : int {
    kRegular = 0,
    kFast = 1,
    kDct = 2,
  };

  explicit Ble(BluetoothRadio& bluetooth_radio);
  ~Ble();

  // Returns true, if BLE communications are supported by a platform.
  bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_);

  // Starts BLE advertising, delivering additional information if the platform
  // supports it.
  //
  // service_id            - The service ID to track.
  // power_level           - The power level to use for the advertisement.
  // advertising_type      - The type of the BLE advertisement.
  // advertisement_bytes   - The connections BLE Advertisement used in
  //                         advertising.
  ErrorOr<bool> StartAdvertising(const std::string& service_id,
                                 PowerLevel power_level,
                                 AdvertisingType advertising_type,
                                 const ByteArray& advertisement_bytes)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // Disables BLE advertising.
  bool StopAdvertising(const std::string& service_id)
      ABSL_LOCKS_EXCLUDED(mutex_);

  bool IsAdvertising(const std::string& service_id) const
      ABSL_LOCKS_EXCLUDED(mutex_);

  bool IsAdvertisingForLegacyDevice(const std::string& service_id) const
      ABSL_LOCKS_EXCLUDED(mutex_);

  // Use dummy bytes to do ble advertising, only for legacy devices.
  // Returns true, if data is successfully set, and false otherwise.
  ErrorOr<bool> StartLegacyAdvertising(
      const std::string& service_id, const std::string& local_endpoint_id,
      const std::string& fast_advertisement_service_uuid)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // (TODO:hais) update this after ble async api refactor.
  // Stop Ble advertising with dummy bytes for legacy device.
  bool StopLegacyAdvertising(const std::string& service_id)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // Adds an alternative BLE service UUID16s for a given Nearby service
  // id. If a device does not support BLE extended advertisements, an alternate
  // service UUID16 may be used to trigger a GATT connection to retrieve GATT
  // characteristics for the Nearby service
  // These alternate uuids are active until the next call to `StopScanning`.
  void AddAlternateUuidForService(uint16_t uuid, const std::string& service_id);

  // Enables BLE scanning for a service ID. Will report any discoverable
  // advertisement data through a callback.
  // Returns true, if the scanning is successfully enabled, false otherwise.
  //
  // service_id  - The service ID to track.
  // pcp         - The PCP to use for the discovery.
  // power_level - The power level to use for the discovery.
  // include_dct_advertisement - Whether to include the dct advertisement in
  //                              the discovery. it is false by default.
  // discovered_peripheral_callback - The callback to invoke for discovery
  //                                  events.
  ErrorOr<bool> StartScanning(const std::string& service_id, Pcp pcp,
                              PowerLevel power_level,
                              bool include_dct_advertisement,
                              DiscoveredPeripheralCallback callback)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // Disables BLE scanning for a service ID.
  // Returns true, if the scanning was previously enabled, false otherwise.
  bool StopScanning(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);

  // Pauses BLE scanning at platform Medium level.
  bool PauseMediumScanning();

  // Resumes BLE scanning at platform Medium level.
  bool ResumeMediumScanning();

  // Returns true if the scanning for service ID is enabled.
  bool IsScanning(const std::string& service_id) const
      ABSL_LOCKS_EXCLUDED(mutex_);

  // Starts a worker thread, creates a Ble socket, associates it with a
  // service id.
  ErrorOr<bool> StartAcceptingConnections(const std::string& service_id,
                                          AcceptedConnectionCallback callback)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // Starts a worker thread, creates a Ble L2CAP socket, associates it with a
  // service id.
  // Returns the PSM of the L2CAP channel on success, or an error code on
  // failure.
  ErrorOr<int> StartAcceptingL2capConnections(
      const std::string& service_id,
      AcceptedL2capConnectionCallback l2cap_callback)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // The refactor version of StartAcceptingConnections() above.
  ErrorOr<bool> StartAcceptingConnections(const std::string& service_id,
                                          AcceptedConnectionCallback2 callback)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // The refactor version of StartAcceptingL2capConnections() above.
  ErrorOr<int> StartAcceptingL2capConnections(
      const std::string& service_id, AcceptedConnectionCallback2 callback)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // Closes socket corresponding to a service id.
  bool StopAcceptingConnections(const std::string& service_id)
      ABSL_LOCKS_EXCLUDED(mutex_);

  bool StopAcceptingL2capConnections(const std::string& service_id)
      ABSL_LOCKS_EXCLUDED(mutex_);

  bool IsAcceptingConnections(const std::string& service_id)
      ABSL_LOCKS_EXCLUDED(mutex_);

  bool IsAcceptingL2capConnections(const std::string& service_id)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // Establishes connection to Ble peripheral.
  // Returns socket instance. On success, BleSocket.IsValid() return true.
  ErrorOr<BleSocket> Connect(const std::string& service_id,
                             const BlePeripheral& peripheral,
                             CancellationFlag* cancellation_flag)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // Establishes connection to Ble peripheral.
  // Returns socket instance. On success, BleSocket.IsValid() return true.
  ErrorOr<BleL2capSocket> ConnectOverL2cap(const std::string& service_id,
                                           const BlePeripheral& peripheral,
                                           CancellationFlag* cancellation_flag)
      ABSL_LOCKS_EXCLUDED(mutex_);

  // The refactor version of Connect() above.
  ErrorOr<std::unique_ptr<mediums::BleSocket>> Connect2(
      const std::string& service_id, const BlePeripheral& peripheral,
      CancellationFlag* cancellation_flag) ABSL_LOCKS_EXCLUDED(mutex_);

  // The refactor version of ConnectOverL2cap() above.
  ErrorOr<std::unique_ptr<mediums::BleSocket>> ConnectOverL2cap2(
      const std::string& service_id, const BlePeripheral& peripheral,
      CancellationFlag* cancellation_flag) ABSL_LOCKS_EXCLUDED(mutex_);

  // Returns true if this object owns a valid platform implementation.
  bool IsMediumValid() const ABSL_LOCKS_EXCLUDED(mutex_) {
    MutexLock lock(&mutex_);
    return medium_.IsValid();
  }

  // Returns true if the BLE device support extended advertisement.
  bool IsExtendedAdvertisementsAvailable() ABSL_LOCKS_EXCLUDED(mutex_) {
    MutexLock lock(&mutex_);
    return medium_.IsExtendedAdvertisementsAvailable();
  };

  // Retrieves a BlePeripheral from a native BLE peripheral ID.
  // On Apple platform, the native ID is NSUUID in string format like
  // "E621E1F8-C36C-495A-93FC-0C247A3E6E5F", other platform will be MAC address
  // as string format like "0C:24:7A:3E:6E:5F".
  std::optional<BlePeripheral> RetrieveBlePeripheralFromNativeId(
      const std::string& ble_peripheral_native_id) {
    MutexLock lock(&mutex_);
    return medium_.RetrieveBlePeripheralFromNativeId(ble_peripheral_native_id);
  }

 private:
  struct AdvertisingInfo {
    mediums::BleAdvertisement medium_advertisement;
    ByteArray dct_advertisement;
    PowerLevel power_level;
    AdvertisingType advertising_type;
  };

  // Same as IsAvailable(), but must be called with `mutex_` held.
  bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);

  // Same as IsAdvertising(), but must be called with `mutex_` held.
  bool IsAdvertisingLocked(const std::string& service_id) const
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);

  // Same as IsAdvertisingForLegacyDevice(), but must be called with `mutex_`
  // held.
  bool IsAdvertisingForLegacyDeviceLocked(const std::string& service_id) const
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);

  // Same as IsScanning(), but must be called with `mutex_` held.
  bool IsScanningLocked(const std::string& service_id) const
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);

  // Same as IsListeningForIncomingConnections(), but must be called with
  // `mutex_` held.
  bool IsAcceptingConnectionsLocked(const std::string& service_id)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  // Same as IsListeningForIncomingConnections(), but must be called with
  // `mutex_` held.
  bool IsAcceptingL2capConnectionsLocked(const std::string& service_id)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);

  bool IsAdvertisementGattServerRunningLocked()
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  bool StartAdvertisementGattServerLocked(const std::string& service_id,
                                          const ByteArray& gatt_advertisement)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  bool GenerateAdvertisementCharacteristic(int slot,
                                           const ByteArray& gatt_advertisement,
                                           GattServer& gatt_server)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  void ProcessFetchGattAdvertisementsRequest(
      BlePeripheral peripheral, int num_slots, int psm,
      const std::vector<std::string>& interesting_service_ids,
      mediums::AdvertisementReadResult& advertisement_read_result)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  bool StopAdvertisementGattServerLocked()
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  ByteArray CreateAdvertisementHeader(int psm,
                                      bool extended_advertisement_advertised)
      ABSL_SHARED_LOCKS_REQUIRED(mutex_);
  // For devices that don't have extended nor gatt adverting.
  api::ble::BleAdvertisementData CreateAdvertisingDataForLegacyDevice();
  bool StartAdvertisingLocked(const std::string& service_id)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  bool StartFastAdvertisingLocked(
      const std::string& service_id, PowerLevel power_level,
      const mediums::BleAdvertisement& medium_advertisement)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  bool StartRegularAdvertisingLocked(
      const std::string& service_id, PowerLevel power_level,
      const mediums::BleAdvertisement& medium_advertisement)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  bool StartGattAdvertisingLocked(const std::string& service_id,
                                  PowerLevel power_level, int psm,
                                  const ByteArray& medium_advertisement_bytes,
                                  bool extended_advertisement_advertised)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  bool StartDctAdvertisingLocked(const std::string& service_id,
                                 PowerLevel power_level,
                                 const ByteArray& dct_advertisement)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  // Called by StartScanning when using the async methods.
  bool StartAsyncScanningLocked(absl::string_view service_id,
                                PowerLevel power_level)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  // Called by StartScanning when using the async methods.
  bool StopAsyncScanningLocked(absl::string_view service_id)
      ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
  api::ble::TxPowerLevel PowerLevelToTxPowerLevel(PowerLevel power_level);
  void RunOnBleThread(Runnable runnable);

  static constexpr int kMaxConcurrentAcceptLoops = 5;

  SingleThreadExecutor serial_executor_;
  ScheduledExecutor alarm_executor_;

  mutable Mutex mutex_;
  BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_);
  BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_);
  BleMedium medium_ ABSL_GUARDED_BY(mutex_){adapter_};
  absl::btree_map<std::string, AdvertisingInfo> advertising_infos_
      ABSL_GUARDED_BY(mutex_);
  std::unique_ptr<GattServer> gatt_server_ ABSL_GUARDED_BY(mutex_);
  absl::flat_hash_map<int, std::pair<std::string, ByteArray>>
      gatt_advertisements_ ABSL_GUARDED_BY(mutex_);
  absl::flat_hash_set<api::ble::GattCharacteristic> hosted_gatt_characteristics_
      ABSL_GUARDED_BY(mutex_);
  absl::flat_hash_set<std::string> scanned_service_ids_ ABSL_GUARDED_BY(mutex_);
  // This map has the same purpose as the set above, but is used only by
  // the async StartScanning method.
  absl::flat_hash_map<std::string,
                      std::unique_ptr<api::ble::BleMedium::ScanningSession>>
      service_ids_to_scanning_sessions_ ABSL_GUARDED_BY(mutex_);
  // Save advertising sessions by service id, used by the async StartAdvertising
  // method.
  absl::flat_hash_map<std::string,
                      std::unique_ptr<api::ble::BleMedium::AdvertisingSession>>
      service_ids_to_advertising_sessions_ ABSL_GUARDED_BY(mutex_);
  std::unique_ptr<CancelableAlarm> lost_alarm_;
  mediums::DiscoveredPeripheralTracker discovered_peripheral_tracker_
      ABSL_GUARDED_BY(mutex_){medium_.IsExtendedAdvertisementsAvailable()};

  // A thread pool dedicated to running all the accept loops from
  // StartAcceptingConnections().
  MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops};

  // A map of service_id -> ServerSocket. If map is non-empty, we
  // are currently listening for incoming connections.
  absl::flat_hash_map<std::string, BleServerSocket> server_sockets_
      ABSL_GUARDED_BY(mutex_);

  // Tracks currently connected incoming sockets. This lets the device know when
  // it's okay to restart GATT server related operations.
  absl::flat_hash_map<std::string, BleSocket> incoming_sockets_
      ABSL_GUARDED_BY(mutex_);

  mediums::InstantOnLostManager instant_on_lost_manager_;

  // A map of service_id -> L2capServerSocket. If map is non-empty, we
  // are currently listening for incoming connections.
  absl::flat_hash_map<std::string, BleL2capServerSocket> l2cap_server_sockets_
      ABSL_GUARDED_BY(mutex_);

  // A map of service_id -> BleL2capSocket.
  // Tracks currently connected incoming sockets. This lets the device know when
  // it's okay to restart L2CAP server related operations.
  absl::flat_hash_map<std::string, BleL2capSocket>
      l2cap_incoming_service_id_to_sockets_ ABSL_GUARDED_BY(mutex_);
};

}  // namespace connections
}  // namespace nearby

#endif  // CORE_INTERNAL_MEDIUMS_BLE_H_
